//先安装一个插件,可以把打印内容显示在编辑器里的插件 Console Ninja
//一.正则字面量和正则对象的差别
    //字面量
    let hd = "houdunren.com";
    let a = "u"
    console.log(/u/.test(hd)) //true
    //console.log(`/${a}/`.test(hd)) //报错
    console.log(eval(`/${a}/`).test(hd)) //true
    //对象
    let reg = new RegExp('u','g')//不要写字面量的两个/了
    //console.log(reg.test(hd)) // true
    let reg1 = new RegExp(a,'g')
    console.log(reg1.test(hd)) //true
//二.replace使用
    console.log('abc'.replace('a','@')) //普通使用 :@bc
    console.log('abc'.replace(/a/g,'@')) //正则使用 :@bc
    console.log("abc".replace(/\w/g,searchResult=>`后盾${searchResult}人`)) //正则加方法使用:后盾a人后盾b人后盾c人
//三.或者选择符 | 
    console.log(/u|@|ren/.test(hd)) //true
    //判断一个电话号是不是北京或者上海的
    let tel = '010-999999999555'
    console.log(/010|020\-\d{7,8}/.test(tel)) //true
    //-前的\表示转义字符,因为-在正则中有特殊的含义
    //\d{7,8}表示7到8位数字
    //以上这个正则有问题:
    let tel2 = '010'
    console.log(/010|020\-\d{7,8}/.test(tel2)) //true
    //这显然不是想要的
    console.log(/010\-\d{7,8}|020\-\d{7,8}/.test(tel2)) //false
    console.log(/010\-\d{7,8}|020\-\d{7,8}/.test(tel)) //true (|的优先级要比其他的低)
    //但是重复了\-\d{7,8}==>原子组
    console.log(/(010|020)\-\d{7,8}/.test(tel)) //true
//四.[]原子表 ()原子组
    let reg4 = /[trr123456]/; //类似let reg4 = /t|r|r|1|2|3|4|5|6/
    let hd4 = 'yyyr234'
    console.log(hd4.match(reg4))
    //match:找出第一个符合正则的'字符'
    let reg42 = /(12|34|56)/; //类似let reg4 = /t|r|r|1|2|3|4|5|6/
    let hd42 = 'yyyr234'
    console.log(hd42.match(reg42))
//五.正则中的转义
    let price = '2323.32w4';
    console.log(/\d+\.\d/.test(price))
    //疑问:为什么后边这个\d可以匹配34两个字符
    //.有两个含义: 1除换行外任何字符 2普通的点
    //同理: \d 表示数字  d只表示字母d
    //难点:正则使用对象方式:
    let reg5 = new RegExp('\d+\.\d')
    console.log(reg5) //d+.d+
    console.log(reg5.test(price)) //false 因为'\d+\.\d'单纯表示一个字符串,这个字符串会转化为d+.d,可以打印一下,会发现打印的是d+.d,这不是我们想要的,所以需要再加一次转义字符
    let reg51 = new RegExp('\\d+\\.\\d')
    console.log(reg51)
    console.log(reg51.test(price)) // \d+\.\d
//六.字符的边界
    let hd6 = 'w232f'
    console.log(/\d/.test(hd6)) //true 表示查找hd6这个字符串,如果能找到一个数字,那么就返回true,否则返回false
    console.log(/^\d/.test(hd6)) //false 表示以数字开头的字符串
    console.log(/\d$/.test(hd6)) //false 表示以数字结尾的字符串
    //输入3-6位的密码,内容是a-z
    console.log("asdsdasdsadsad".match(/[a-z]{3,6}/)) //
    console.log(/[a-z]{3,6}/.test("asdsdasdsadsad")) 
    //把正则当成一个工具,从头扫描,看是否可以满足,如果满足了就不继续了(没有标记开头结尾)
    //正确做法:
    console.log("asds".match(/^[a-z]{3,6}$/)) //
    console.log(/^[a-z]{3,6}$/.test("asdsdf")) 
//七.正则的元字符
    let hd7 = 'houdunren 2010 2013'
    console.log(hd7.match(/\d+/))// 如果正则最后不带g,那么就匹配一次就停止
    console.log(hd7.match(/\d+/g)) //g:全局匹配 ['2010','2013']
    let hd71 = `
        张三:010-9999999,李四:020-8888888
    `
    console.log(hd71.match(/\d{3}-\d{7,8}/))
    console.log(hd71.match(/\d{3}-\d{7,8}/g))

    console.log(hd7.match(/\D+/))
    //拿到hd71的张三和李四
    console.log(hd71.match(/[-\d:,]/g))
    console.log(hd71.match(/[^-\d:,]+/g))
    console.log(hd71.match(/[^-\d:,\s]+/g))
    //空白 \s 包括换行,空格,制表符
    console.log(/\s/.test(' hd'))
    console.log(/\s/.test('\nhd'))
    console.log(/\s/.test(' hd\n'))
    console.log(/\s/.test('\thd'))
    console.log(/\S/.test('\nhd'))// \S表示不是空白,所以从头往后找,找到了h,所以是true
    //\w 字母数字下划线
    //邮箱的匹配
    let email = '2425308760@qq.com'
    console.log(email.match(/^\w+@\w+\.\w+$/))
    //\W
    console.log('hdcms@'.match(/\W/));
    //用户名 字母开始后跟字母数字下划线 5-10位
    console.log(/^[a-z]\w{4,9}$/i.test('abc22'))
    //. 包含除了换行符的所有字符
    let hd72 = 'houdunren&^(*^*(^'
    console.log(hd72.match(/.+/))
    let url = 'https://www.baidu.com'
    console.log(url.match(/https?:\/\/\w+\.\w+\.\w+/))

    let urls = `
    houdunren.com
    dhcms.com
    `
    console.log(urls.match(/.+/)[0]);
   // console.log(urls.match(/.+/));
    //s模式:把换行符也当成一般字符的模式

    let tel72 = '010 - 999999999'
    console.log(tel72.match(/\d+ - \d{7,8}/))//空格也是普通字符,这里也可以把空格换成\s,但不可以不写
    //匹配所有字符(. \d \w 都有一定的范围)
    let hd73 = `
    <span>
        ds@@@
        houdunren
    </span>
    `
    let xj = "ab"
    console.log(xj.match(/b/))

    console.log(hd73.match(/<span>[\d\D]+<\/span>/))
//八.模式修正符
    //s:把空白当作普通字符
    let hd81 = 'houdunren'
    console.log(hd81.match(/U/));
    console.log(hd81.match(/u/ig));
    console.log(hd81.replace(/u/ig,'@'))
//九.多行匹配
    let prices = `
        #1 js,200元 #
        #2 php,300元 #
        #9 houdunren.com # 后盾人
        #3 node.js,180元 #
    `
    //第三行不符合规范,排除
    //对每一行区别对待:修饰符m=>开始和结尾使用每一行的开始和结尾,而不是整个字符的
    console.log(prices.match(/^\s*#\d+\s+\S+\s+#$/gm).map(item=>{
        item = item.replace(/\s*#\d+\s*/,"").replace(/\s+#/,"")
        let [name,price] = item.split(',')
        return {name,price}
    }))
//十.字符属性和中文匹配 //任何字符都有自己的字符属性,比如字母有自己的属性[L] 必须结合u使用
    //\p后边跟大括号里放的属性,表示检测每一个字符的属性
    //\p{L}表示字母(包括中文)
    //\p{N}表示数字
    //\p{Z}表示空白
    //\p{S}表示非空白
    //\p{P}表示标点符号
    let hd10 = 'houdunren2010.不断发布教程,加油！'
    console.log(hd10.match(/\p{P}/gu))
    //另外每个字符都有自己的语言系统,说明自己是中文还是韩文啊等等
    console.log(hd10.match(/\p{sc=Han}/gu))

    let str = ""
 //十一、exec的使用场景以及lastindex
    //如果只使用match方法，那么正则如果带g模式匹配，那么查询的结果全，但是展示的信息不全
    //如果不带g，那么结果不全，只显示第一个查询到的结果，但是的信息全
    //如果需要展示的信息全，查询的也全，那么就需要正则方法exec
    //每次exec，正则的查询索引就会往后延续，怎么看索引，就是正则的lastindex
    let hd11="houdunren"
    let reg11=/\w/g
    console.log(reg11.lastIndex)
    console.log(reg11.exec(hd11))
    console.log(reg11.lastIndex)
    console.log(reg11.exec(hd11))
    //常常这样使用，达到match+g且展示ok 
    while(res=reg11.exec(hd11)){
        console.log(res)
    }   
//十二、原子表
    let hd12 = "houdunren"
    console.log(hd12.match(/ue/));//match null
    console.log(hd12.match(/[ue]/g));
    //检索字符串中的日期
    let string12="我的生日是1998/05/24，她的生日是1999-05-27,他的生日是1999/09-23"
    let reg12 = /\d{4}[-\/]\d{2}[-\/]\d{2}/g
    console.log(string12.match(reg12))
    //有个bug，如果是1998/05-12也能检索到，使用原子组优化
    let reg121 = /\d{4}([-\/])\d{2}\1\d{2}/g
    console.log(string12.match(reg121))
    //原子表不解析特殊字符
    let hd122 = "(houdunren).+"
    console.log(hd122.match(/[(hn.+)]/gi))
//十三、区间匹配和排除匹配
    let hd13 = "2010+daJ"
    console.log(hd13.match(/[0-9]+\+[a-z]/g))
    let hd131 = "张三：010-999999，李四：010-88888"
    console.log(hd131.match(/[^\d：，\-]+/g))
//十四、原子表匹配所有内容(.表示出了换行符所有字符，也包括空字符)
    let hd14=`
        houdunren
        hdcms
    `
    console.log(hd14.match(/.+/g))//这样会匹配到三处，'    houdnrn'，'   hdcms'，'   '
    //如果想匹配所有内容：  [\s\S] [\d\D]  [.\n]
    console.log(hd14.match(/[\s\S]+/g))
    console.log(hd14.match(/[\d\D]+/g))
    console.log(hd14.match(/(.|[\n])+/g))
//十五、原子组
    let hd15= `
    <h1>
        houdunren
        </h1>
    <h2>dhcms</h2>
    `
    //拿到所有的h元素及其内容
    let reg15 = /<(h[1-6])>[\s\S]*<\/\1>/i
    console.log(hd15.match(reg15))
    //如果正则没有添加g，那么match的返回结果是数组
    //这个数组的0索引是匹配到的最终结果，1索引是第一个原子组的结果，以次类推
    //index是lastindex，input是reg15，groups下面讲
    //所以match不止用于匹配全部结果，也可以用于匹配原子组对应的结果'
//十六、邮箱验证通过原子组
    let mail16 = "2425308760@qq.com"
    let reg16 = /^[\w-]+@[\w-]+\.(com|org|cn|net)$/i
    console.log(mail16.match(reg16))
    //问题：
    mail16= "2425@dcms.com.cn"
    console.log(mail16.match(reg16))//匹配失败
    let reg162= /^[\w-]+@([\w-]+\.)+(com|org|cn|net)$/i
    console.log(mail16.match(reg162))
//十七、把h标签替换为p标签
    let hd17 = `
        <h1>houdunen</h1>
        <span>后盾人</span>
        <h3>hdcms</h3>
    `
    let reg17 = /<(h[1-6])>([\s\S]*)<\/\1>/gi
    hd17.replace(reg17,`<p>$2</p>`)
    hd17.replace(reg17,(p0,p1,p2)=>{
        return `<p>${p2}</p>`
    })
    console.log(hd17)
//十八、嵌套分组和不使用组
    let hd18 = `
    https://www.houdunren.com
    https://houdunwang.com
    `
    let reg18 = /https?:\/\/(\w+\.\w+\.(com|org|cn))/i
    console.log(hd18.match(reg18))
    //发现结果中数组第三项是com，我们不需要的话，就可以加个?:
    let reg181 = /https?:\/\/(\w+\.\w+\.(?:com|org|cn))/ig
    console.log(hd18.match(reg181))
    let reg182=/https?:\/\/((?:\w+\.)?\w+\.(?:com|org|cn))/ig //这里问号比较多，注意分清楚什么意思
    console.log(reg182.exec(hd18))
    console.log(reg182.exec(hd18))
//十九、重复匹配
    let hd19='dddddd'
    console.log(hd19.match(/d{1,3}/))// 匹配1-3个，那么根据默认贪婪的原则，就会匹配三个
    console.log(hd19.match(/d{1,}/))//匹配1-无数个，这里{1,} 等价+
    console.log("hdhdhd33hd".match(/(hd)+/)) //注意，这里的返回值里1索引是'hd'，而不是hdhdhd
//二十、禁止贪婪
    let hd20 = "hdddddd"
    console.log(hd20.match(/hd+?/))//如果不加问号，那么+会倾向于多的一侧，如果加了问号，那么+会倾向于少的那一侧，也就是1个
    console.log(hd20.match(/hd{2,5}/))//贪婪
    console.log(hd20.match(/hd{2,5}?/))//禁止贪婪
    console.log(hd20.match(/hd??/))//0-1个取0个
//二十一、matchAll解决match+g模式不能展示数据全的问题
    let content =  `
        <h1>houdunren</h1>
        <h2>hdcms</h2>
        <h3>hdc2ms</h3>
    `
    let reg21 = /<(h[1-6])>([\s\S]*?)<\/\1>/ig
    let reg212 = /<(h[1-6])>([\s\S]*?)<\/\1>/i
    console.log(content.match(reg21))//展示三个，但是三个数据都不全
    console.log(content.match(reg212))//展示匹配到的第一个，但是展示的数据全
    console.log(content.matchAll(reg21))//可迭代对象
    for(let i of content.matchAll(reg21)){
        console.log(i)
    }
//二十二、手写matchAll
    String.prototype.myMatchAll = function(reg){
        let res = this.match(reg)
        if(res){
            let str = this.replace(res[0],"^".repeat(res[0].length))
            let match = str.myMatchAll(reg) || [];
            return [res,...match];
        }
    }
    let hd22 = "hdwhdhwdhd"
    let reg22 = /w/
    console.log(hd22.myMatchAll(reg22))
 //二十三、使用正则全局匹配,参考十一
    let hd23 = "houdunren"
    let reg23 = /u/gi
    while(res=reg23.exec(hd23)){
        console.log(res)
    }
    //封装search
    function search(string,reg){
        let result = []
        while(res=reg.exec(string)){
            result.push(res)
        }
        return result
    }
    console.dir(search(hd23,reg23))
//二十四、常用方法总结
    //字符串方法search
    let hd24 =  "houdunren.com"
    console.log(hd24.search("d"))//返回索引,如果没有，返回-1
    console.log(hd24.search(/d/))//返回索引,如果没有，返回-1
    //match
    console.log(hd24.match(/d/))
    //split
    console.log("2020/01-25".split(/[-\/]/))
    //replace
    console.log(hd24.replace(/u/,"U"))
    let tel24 = "(010)9999993 (020)8888888"
    let reg24 = /\((\d{3,4})\)(\d{7,8})/g;
    console.log(tel24.replace(reg24,"$1-$2"))
    //如果replace第二个参数不使用函数的形式，而使用字符串的话，那么可以填充的有：
    // &`表示匹配到的内容的左边一个字符，
    // &'表示匹配到的内容的右边一个字符，
    // $`表示匹配到的内容的右边一个字符，
    // $&表示匹配到的内容
    let hd242 = "=后盾人+"
    console.log(hd242.replace(/后盾人/,"$`$&$`"))
    //结果是==后盾人=+ ，因为相当于把后盾人三个字变成了=后盾人=，再加上原先就有的=和+
//二十五、一个小练习，使用replace完成某些业务需求
    //把后盾人的网址变成https,并且加上www.
    let links =  `
        <a style="color:red" href="http://www.hdcms.com">
            开源系统
        </a>
        <a id="l1" href="http://houdunren.com">后盾人</a>
        <a href="http://www.qq.com">腾讯</a>
        <h4>http://www.sina.com</h4>
    `
    const reg25 = /(<a.*href=['"])(http)(:\/\/)(www\.)?(hdcms|houdunren)/ig
    let linksAfter = links.replace(reg25, (v,...args)=>{
        args[1]+="s"
        args[3] = args[3] || "www."
        return args.splice(0,5).join("")
    })
    console.log(linksAfter)
//二十六、原子组起别名
    let hd26 = `
        <h1>houdunren</h1>
        <span>后盾人</span>
        <h2>hdcms</h2>
    `
    // const reg26 = /<(h[1-6])>(.*?)<\/\1>/gi
    // console.log(hd26.replace(reg26,"<h4>$2</h4>"))
    //可以给组起个别名
    const reg261 = /<(h[1-6])>(?<content>.*?)<\/\1>/gi //?<content>就是给这个组起了个别名，叫做content，下面是用的时候就可以$<content>
    console.log(hd26.replace(reg261,"<h4>$<content></h4>"))
//二十七、原子组起别名，并且优化，并且认识一下groups
    //拿到一个数组，数组的每一项是一个对象，对象里包括link值，title值
    let links27 =  `
        <a style="color:red" href="http://www.hdcms.com">
            开源系统
        </a>
        <a id="l1" href="http://houdunren.com">后盾人</a>
        <a href="http://www.qq.com">腾讯</a>
        <h4>http://www.sina.com</h4>
    `
    const reg27 = /<a.*?href=(['"])(?<link>.*?)\1>(?<title>[\s\S]*?)<\/a>/g
    //console.log(links27.match(reg27))
    let result27 = []
    for(let i of links27.matchAll(reg27)){
        result27.push(i.groups)
    }
    console.log("result27",JSON.stringify(result27))
//二十八、断言匹配（后置断言）
    let str28 = "后盾人不断分享视频教程，学习后盾人教程提升编程能力"
    //需求：把后盾人后边的教程加上超链接形式
    let reg28 = /后盾人(?=教程)/g //(?=)相当于加条件语句，但不会匹配到(?=)里边的内容
    str28 = str28.replace(reg28,`<a href="http://www.houdunren.com">$&</a>`)
    console.log(str28)
    //需求：使用断言匹配规范价格.都加上.00
    let lessons = `
        js,200元,300次
        php,300.00元,400次
        html,400元,500次
    `
    let reg281 = /(\d+)(\.00)?(?=元)/g
    lessons = lessons.replace(reg281,(v,...args)=>{
        console.log("v:",v)//可以看到匹配到的值，这里只有200 、300.00 而不会有元这个字
        args[1] = args[1] || '.00'
        return args.splice(0,2).join("")
    })
    console.log(lessons)
//二十九、断言匹配（前置断言）
    //需求：把所有url换成 https://www.houdunren.com
    let links29 = `
        <a href="http://www.baidu.com">百度</a>
        <a href="http://www.google.com">谷歌</a>
    `
    const reg29 = /(?<=href=(['"])).+(?=\1)/ig
    console.log(links29.match(reg29))
    links29 = links29.replace(reg29,"https://www.houdunren.com")
    console.log(links29)
    //需求：把电话号最后几位数字变成*
    let users = `
        向军电话 12345678901
        后盾人电话 12345678902
    `
    let reg291 = /(?<=\d{7})\d{4}/gi
    users = users.replace(reg291,v=>{
        return '*'.repeat(v.length)
    })
    console.log(users)
//三十、零宽负向先行断言 ?=后置断言  ?!后置非断言   限制后边不是谁
    //获取后面不是数字的字母，我们想获取的是hdcms
    let hd30 = "houdunren2010hdcms"
    let reg30 = /[a-z]+(?!\d+)/i
    console.log(hd30.match(reg30))
    //实际这样获取的是houdunre
    let reg30_1 = /[a-z]+(?!\d+)$/i//在最后加一个$
    console.log(hd30.match(reg30_1))
    //需求：字符中不能有向军这两个字,且是5-6位，且以a-z开始结束
    let hd301 = "houdunren向军2010hdcms"
    let reg301 = /^(?!.*向军.*)[a-z]{5,6}$/i;
    console.log(reg301.test(hd301))
//三十一、零宽负向后行断言 限制前边不是谁?<!
    //获取前面不是数字的字符串，即houdunren
    let hd31 = "houdunren2010hdcms"
    let reg31 = /(?<!\d+)[a-z]+/i
    console.log(hd31.match(reg31))
//三十二、把所有后盾人的子网址变成oss.houdunren.com开头
    let hd32 = `
        <a href="http://www.houdunren.com/index">后盾人</a>
        <a href="http://oss.houdunren.com/hdcms">后盾CMS</a>
        <a href="http://cns.houdunren.com/hdcms/hdnet">后盾NET</a>
        <a href="http://houdunren.com/hdcms/hdnet2">后盾NET2</a>
    `
    const reg32 = /http:\/\/([a-z]+)?(?<!oss)\..+?(?=\/)/gi
    console.log(hd32.match(reg32))
    hd32 = hd32.replace(reg32,v=>{
        return "http://oss.houdunren.com"
    })
    console.log(hd32)

    //解释一下：这种很复杂的正则表达式，先把断言删掉=>/https:\/\/([a-z]+)?\..+?/gi
    //然后执行一下
    //console.log(hd32.match(/http:\/\/([a-z]+)?\..+?/gi))
    //然后再加上其中一个断言=>/https:\/\/([a-z]+)?\..+?(?=\/)/gi  这个断言是修饰前边的.+?的，即最后一个字符的后边需要是/
    //console.log(hd32.match(/http:\/\/([a-z]+)?\..+?(?=\/)/gi))
    //然后再加上另外一个断言=>/http:\/\/([a-z]+)?(?<!oss)\..+?(?=\/)/gi  (?<!oss)断言是修饰后边的.的，即后边这个点的前边必须 不 是oss
    